//TODO: test RandomNoise, Maby combine with Noise Filter as another mode?

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// RandomNoise is an algorithm to generate random noise in an image of
    /// either type Gaussian or Shot.
    /// </summary>
    public class RandomNoise
    {

    //  //The width and height of the output
        private int d_w;
        private int d_h;

        private int[] dest_1d;


        //A random number generator
        private Random randgen = new Random();

        /// <summary>
        /// ShotFull applies full shot noise to the input image. This means that it
        /// a) assume the image is grey level (hence RR=GG=BB)
        /// b) use value 0x000000ff to get the BB value
        /// c) apply the operation (eg create noise). This depends on the type of noise which must be created. Different methods create different noise. 
        /// d) clip to lie from 0 to 255 (if necessary). Call this value 0xCD
        /// e) create int value 0xffCDCDCD
        /// </summary>
        /// <param name="src_1d">src_1d The source image as a pixel array</param>
        /// <param name="width">width of the destination image in pixels</param>
        /// <param name="height">height of the destination image in pixels</param>
        /// <param name="par">This is used to represent the probability that a pixel will changed.</param>
        /// <returns>pixel array containing the noisy input image</returns>
        public int [] ShotFull(int [] src_1d, int width, int height, float par)
        {
         d_w = width;
         d_h = height;
         dest_1d = new int[d_w * d_h];
         double ran1;
         double ran2;
         int src_rgb;
         int result = 0;

         for (int i = 0; i < dest_1d.Length; i++)
         {

             //Select required bits from 32 bit integer
             src_rgb = src_1d[i] & 0x000000ff;

             ran1 = randgen.NextDouble();

             if (ran1 < par)
             {

                 //Only change the bits which have passed the above test

                 ran2 = randgen.NextDouble();

                 //Change them to either 0 or 255
                 if (ran2 < 0.5) { result = 0; }
                 else if (ran2 >= 0.5) { result = 255; }
             }


             //Otherwise leave them alone
             else result = src_rgb;

             //Convert back to 32 bit integer value
             dest_1d[i] = (int)(0xFF << 24 | (result + (result << 16) + (result << 8)));
         }
         return dest_1d;

     }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="src_1d"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="par"></param>
        /// <returns></returns>
        public int[] ShotPartial(int[] src_1d, int width, int height, float par)
        {

            d_w = width;
            d_h = height;
            dest_1d = new int[d_w * d_h];
            double ran1;
            double ran2;
            int src_rgb;
            int result = 0;

            for (int i = 0; i < dest_1d.Length; i++)
            {

                //Select required bits from 32 bit integer
                src_rgb = src_1d[i] & 0x000000ff;

                ran1 = randgen.NextDouble();

                if (ran1 < par)
                {

                    //Only change the bits which have passed the above test

                    ran2 = randgen.NextDouble();

                    //Change them to a random value between 0 and 255

                    result = (int)Math.Floor(255 * ran2);
                }


                //Otherwise leave them alone
                else result = src_rgb;

                //Convert back to 32 bit integer value
                //TODO
                //dest_1d[i] = 0xFF << 24 | (result + (result << 16) + (result << 8));
            }
            return dest_1d;

        }

        /// <summary>
        /// Gaussian adds gaussian noise to the input image. This means that 
        /// it adds a gaussian distributed random number, with standard deviation par
        /// to the current pixel. This is repeated across the whole image.
        /// </summary>
        /// <param name="src_1d">The source image as a pixel array</param>
        /// <param name="width">width of the destination image in pixels</param>
        /// <param name="height">height of the destination image in pixels</param>
        /// <param name="par">This represents the Standard Deviation by which the Gaussian noise is generated</param>
        /// <returns>A pixel array containing the noisy input image</returns>
        public int[] Gaussian(int[] src_1d, int width, int height, float par)
        {

            d_w = width;
            d_h = height;
            dest_1d = new int[d_w * d_h];
            double ran1 = 0.0f;
            int src_rgb;
            int result = 0;

            for (int i = 0; i < dest_1d.Length; i++)
            {

                //Select required bits from 32 bit integer
                src_rgb = src_1d[i] & 0x000000ff;

                //Generate amount to be added
                //Todo
                //ran1 = (par * (randgen.nextGaussian()));
                result = (int)(src_rgb + ran1);

                //Clip final result
                if (result < 0) { result = 0; }
                if (result > 255) { result = 255; }

                //Convert back to 32 bit integer value
                dest_1d[i] = (int)(0xFF << 24 | (result + (result << 16) + (result << 8)));
            }

            return dest_1d;
        }
    }
}